#pragma once

#include <cppunit/config/SourcePrefix.h>
#include <iostream>
#include <string>
#include "TestVarTable.h"
#include "Utilities.h"

void VarTableTest::setUp(){
	validNameInput.push_back("abcdef");
	validNameInput.push_back("a1b2c3d4e5f6");
	validNameInput.push_back("x");
	validNameInput.push_back("n#u3s");
	validNameInput.push_back("1@3");
	validNameInput.push_back("-5");
	validNameInput.push_back("0");
	validNameInput.push_back("1");
	validNameInput.push_back("&");
	validNameInput.push_back("~");

	invalidNameInput.push_back("");

	invalidIndexInput.push_back(0);
	invalidIndexInput.push_back(-1);
	invalidIndexInput.push_back(-1000);

	nonExistNameInput.push_back("bad");
	nonExistNameInput.push_back("13");
	nonExistNameInput.push_back("***");

	nonExistIndexInput.push_back(99);
	nonExistIndexInput.push_back(78);
	nonExistIndexInput.push_back(1234);

	unsigned int n;
	for(n=0; n<validNameInput.size(); n++){
		expectedIndexList.push_back(n+1);
	}

	varTb = VarTable();
}

void VarTableTest::tearDown(){
	validNameInput.clear();
	nonExistIndexInput.clear();
	nonExistNameInput.clear();
	invalidIndexInput.clear();
	expectedIndexList.clear();
}

// Registers the fixture into the 'registry'
CPPUNIT_TEST_SUITE_REGISTRATION( VarTableTest );

void VarTableTest::testInsert(Type::List::Name nameList, Type::List::Var indexList, 
	bool pass, std::string failMsg){
		int tmpIndex, bufIndex;
		Type::Name::Var tmpName;
		unsigned int t;
		bool tmpTF;

		for(t=0; t<nameList.size(); t++){
			tmpName = nameList.at(t);
			tmpIndex = varTb.insertVar(tmpName);

			bufIndex = indexList.at(Utilities::getIndexBoundedBySize(t, indexList.size()));
			tmpTF = tmpIndex == bufIndex;

			if(!pass){
				tmpTF = !tmpTF;
			}
			CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(failMsg, CPPUNIT_ASSERT(tmpTF));
		}
}

void VarTableTest::testGetIndices(Type::List::Name nameList, bool isNamePresent, Type::List::Var indexList,
	bool pass, std::string failMsg){
		Type::List::Var tmpVarList;
		int tmpIndex, bufIndex;
		Type::Name::Var tmpName;
		unsigned int t;
		bool tmpTF;

		if(isNamePresent){
			for(t=0; t<nameList.size(); t++){
				tmpName = nameList.at(t);
				tmpIndex = varTb.getVarIndex(tmpName);

				bufIndex = indexList.at(Utilities::getIndexBoundedBySize(t, indexList.size()));
				tmpTF = tmpIndex == bufIndex;

				if(!pass){
					tmpTF = !tmpTF;
				}
				CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(
					failMsg + " (with name present)", CPPUNIT_ASSERT(tmpTF));
			}
		}else{
			tmpVarList = varTb.getVarIndices();

			tmpTF = tmpVarList.size() == indexList.size();
			CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(
				failMsg + " (without name present, size different)", CPPUNIT_ASSERT(tmpTF));

			for(t=0; t<tmpVarList.size(); t++){
				tmpIndex = tmpVarList.at(t);
				bufIndex = indexList.at(Utilities::getIndexBoundedBySize(t, indexList.size()));
				tmpTF = tmpIndex == bufIndex;

				if(!pass){
					tmpTF = !tmpTF;
				}
				CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(
					failMsg + " (without name present)", CPPUNIT_ASSERT(tmpTF));
			}
		}
}

void VarTableTest::testGetNames(Type::List::Var indexList, Type::List::Name nameList, 
	bool pass, std::string failMsg){

		int tmpIndex;
		Type::Name::Var tmpName, bufName;
		unsigned int t;
		bool tmpTF;

		for(t=0; t<indexList.size(); t++){
			tmpIndex = indexList.at(t);
			tmpName = varTb.getVarName(tmpIndex);

			bufName = nameList.at(Utilities::getIndexBoundedBySize(t, nameList.size()));
			tmpTF = tmpName.compare(bufName) == 0;

			if(!pass){
				tmpTF = !tmpTF;
			}
			CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(failMsg, CPPUNIT_ASSERT(tmpTF));
		}
}

void VarTableTest::testIsExists(Type::List::Name nameList, Type::List::Var indexList, 
	bool pass, std::string failMsg){
		Type::Index::Var tmpIndex;
		Type::Name::Var tmpName;
		bool tmpTF;
		unsigned int t;

		for(t =0 ; t < indexList.size(); t++){
			tmpIndex = indexList.at(t);
			tmpTF = varTb.isVarExistsInVarTable(tmpIndex);

			if(!pass){
				tmpTF = !tmpTF;
			}

			CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(
				failMsg + " (index existence)" ,CPPUNIT_ASSERT(tmpTF));
		}

		for(t =0 ; t < nameList.size(); t++){
			tmpName = nameList.at(t);
			tmpTF = varTb.isVarExistsInVarTable(tmpName);

			if(!pass){
				tmpTF = !tmpTF;
			}

			CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(
				failMsg + " (name existence)" ,CPPUNIT_ASSERT(tmpTF));
		}
}

// method to test the insertion and retrieval of variable
void VarTableTest::testInsert(){
	std::cout<<std::endl<< "VarTable Insert()"<<std::endl;

	// verify variable insertion with invalid name input will return -1
	msg = "Failed at insertion of invalid variable name";
	testInsert(invalidNameInput, Type::List::Var(invalidNameInput.size(), -1), true, msg);

	// verify variable insertion with valid name input will not return -1
	msg = "Failed at insertion of valid variable name";
	testInsert(validNameInput, expectedIndexList, true, msg);

	// verify variable insertion with valid name input will return same indices
	msg = "Failed at insertion of repeating valid variable name";
	testInsert(validNameInput, expectedIndexList, true, msg);
}

void VarTableTest::testGetIndices(){
	std::cout<<std::endl<< "VarTable GetIndices()"<<std::endl;

	testInsert();

	// verify all variable indcies stored
	msg = "Failed at getting all indices";
	testGetIndices(Type::List::Name(), false, expectedIndexList, true, msg);

	// verify the correctness of variable name - index records
	msg = "Failed at getting indices from valid names";
	testGetIndices(validNameInput, true, expectedIndexList, true, msg);

	// verify that there should not be any records of invalid variable name being insereted
	msg = "Failed at getting indices from invalid names";
	testGetIndices(invalidNameInput, true, Type::List::Proc(1, -1), true, msg);

	// verify that there should not be any records of not inserted variable name
	msg = "Failed at getting indices from not inserted names";
	testGetIndices(nonExistNameInput, true, Type::List::Proc(1, -1), true, msg);
}

void VarTableTest::testGetNames(){
	std::cout<<std::endl<< "VarTable GetNames()"<<std::endl;

	testInsert();

	// verify variable name retrieval with the expected proc index
	msg = "Failed at getting variable name from expected index";
	testGetNames(expectedIndexList, validNameInput, true, msg);

	// verify variable name retrieval with the invalid variable index will return empty string
	msg = "Failed at getting variable name from invalid index";
	testGetNames(invalidIndexInput, Type::List::Name(1, ""), true, msg);

	// verify variable name retrieval with the non existing proc index will return empty string
	msg = "Failed at getting proc name from not inserted index";
	testGetNames(nonExistIndexInput, Type::List::Name(1, ""), true, msg);
}

// method to test the existence of procedure
void VarTableTest::testIsExists(){
	std::cout<<std::endl<< "VarTable Exist()"<<std::endl;

	testInsert();

	// verify the existence of valid variable name and expected variable indices to be stored
	msg = "Failed at checking existence of valid variable name, expected variable indcies";
	testIsExists(validNameInput, expectedIndexList, true, msg);

	// verify the existence of invalid variable name and indices not to be stored
	msg = "Failed at checking existence of invalid variable name and indcies";
	testIsExists(invalidNameInput, invalidIndexInput, false, msg);

	// verify the existence of not inserted variable name and indices not to be stored
	msg = "Failed at checking existence of not inserted variable name and indcies";
	testIsExists(nonExistNameInput, nonExistIndexInput, false, msg);
}